 /*
 *  Author - Hank B <gohsthb@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include <avr/io.h>
#include <inttypes.h>
#include <avr/interrupt.h>
#include "WB_Power.h"

// define the different states
enum { off_State, on_State, buzz_State };

// variables
volatile uint32_t systemMillis;
uint32_t currentMillis;
uint32_t LED_millis=0;
const uint32_t LED_Interval = 1000ul;
uint32_t buzz_millis;
const uint32_t buzzOn_Interval = buzzOn_time;
const uint32_t buzzOff_Interval = buzzOff_time;
uint8_t state=off_State;
uint8_t buttonState;
uint32_t Timer;
uint8_t count;
uint8_t lastButtons = 0;
uint32_t lastChange = 5000;

typedef struct flgReg {
  uint8_t BUZZstate_flg :1;
} flgReg_t;
#define flags (*(volatile flgReg_t*)_SFR_MEM_ADDR(GPIOR0))

// function prototypes
uint8_t checkButtons(void);
uint8_t buttonHeld(void);
uint32_t millis(void);

int main(void) {
	// power reduction
	ACSR |= (1<<ACD); // Disable analog comparator
	PRR |= (1<<PRUSI)|(1<<PRADC); // turn off clocks to the adc and usi

	// setup for millis function
	TCCR0A = 0x02;  // CTC mode
	OCR0A = 125;    // set for 1 ms rate
	TIMSK |= (1<<OCIE0A);    // enable interrupt
	TCCR0B = 0x03;  // /64 prescaler, start timer0

#ifndef BUZZER
	// Setup for Piezo Speaker frequency
	// add setup for TMR1 to output PWM on OCR1
	OCR1A = 127;
	TCCR1 = ((1<<PWM1A)|(1<<COM1A1)); // enable PWM1A, cleared on match
	TCCR1 |= 0x05; // /16 prescaler; ~2kHz output frequency

	// Setup IO port
	PORTB = ((1<<Button1)|(1<<Button2)); // turn on pullups for buttons
#else
	PORTB = ((1<<Button1)|(1<<Button2)|(1<<Buzzer)); // turn on pullups for buttons, and buzzer
#endif
	DDRB = 0xFF;
	DDRB &= ~((1<<Button1)|(1<<Button2)); // input pins

	sei(); // enable interrupts
	
	while(1) {
		switch(state) {
		case off_State:
			// power is off, waiting for button push
			PORTB &= ~(1<<Relay); // turn off the relay
			flags.BUZZstate_flg = 0; // make sure buzzer is off
			break;
		case on_State:
			// power is on, just running the timer now
			if(Timer < millis()) {
				state = buzz_State;
				Timer = Warning_time;
				buzz_millis = millis() + buzzOn_Interval;
				flags.BUZZstate_flg = 1;
			};
			break;
		case buzz_State:
			// buzz every 30 seconds for 1 second
			if(millis() > buzz_millis) {
				if(flags.BUZZstate_flg) {
					// buzzer was on so turn it off
					flags.BUZZstate_flg = 0;
					buzz_millis += buzzOff_Interval;
				} else {
					// buzzer was off so turn it on
					flags.BUZZstate_flg = 1;
					buzz_millis += buzzOn_Interval;
				}
			}
			if(Timer < millis())
				state = off_State; // turn power off
			break;
		default:
			state = off_State; // not sure how we got here but turn off
			break;
		}; // end switch(state)

		// if a button is pushed set the timer and turn the power on
		if(checkButtons()) {
			buttonState = checkButtons();
			switch(buttonState) {
			case 1: // one Hour
				Timer = millis() + (Button1_time - Warning_time);
				state = on_State;
				break;
			case 2: // two Hours
				Timer = millis() + (Button2_time - Warning_time);
				state = on_State;
				break;
			case 3: // three Hours
				Timer = millis() + (Button3_time - Warning_time);
				state = on_State;
				break;
			default:
				state = off_State;
				break;
			};
		}

		// if a button is held down then turn off relay
		if(buttonHeld())
			state = off_State;	// turn off relay if button held

		// turn the buzzer on/off as nessesary
		if(flags.BUZZstate_flg)
			DDRB |= (1<<Buzzer); // turn on the buzzer
		else
			DDRB &= ~(1<<Buzzer); // turn off the buzzer

		// Heart beat LED
		if(millis() > LED_millis) {
			LED_millis += LED_Interval;
			if(++count % 2)
				PORTB |= (1<<LEDPin);
			else
				PORTB &= ~(1<<LEDPin);
		}
				
	}
}

// check if a button is pushed
// returns 0 if no buttons are pushed, or both buttons are pushed
// otherwise returns the button number
uint8_t checkButtons(void) {
	// make a copy of portb and ddrb
	uint8_t portb_copy = PORTB;
	uint8_t ddrb_copy = DDRB;
	
	DDRB &= ~((1<<Button1)|(1<<Button2)|(1<<Button3)); // change all button pins to inputs
	PORTB |= ((1<<Button1)|(1<<Button2)|(1<<Button3)); // enable pullups for buttons

	uint8_t tmp = ~PINB & ((1<<Button1)|(1<<Button2)|(1<<Button3)); // read PORTB, mask for the buttons only
	// above changed per suggestion from Mike B
	// Buttons will show '1' if pushed

	PORTB = portb_copy;	// restore the previous states
	DDRB = ddrb_copy;

	if(tmp == lastButtons) {
		if(millis() > (lastChange + 20)) { // button must be held for > 20ms
		// a button has been pushed
			if(tmp == (1<<Button1)) // the button pin gets pulled low
				return 1;
			if(tmp == (1<<Button2))
				return 2;
			if(tmp == (1<<Button3))
				return 3;
		}
	} else {
		lastButtons = tmp;
		lastChange = millis();
	}
	return 0;
}

// check if a button is being held down
uint8_t buttonHeld(void) {
	if(checkButtons() != 0) {// only one of the buttons is pushed
		if(millis() > (lastChange + 6000))
			return 1;
	}
	return 0;
}

// this is the milli-second interrupt
ISR(TIM0_COMPA_vect) {
	systemMillis = systemMillis + 1;
}

uint32_t millis(void) {
  uint32_t m;
  uint8_t cSREG = SREG;
  
  cli();
  m = systemMillis;
  SREG = cSREG;
  
  return m;
}
